package edu.northwestern.cbits.purple_robot_manager.probes.features;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.plugins.OutputPlugin;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
import edu.northwestern.cbits.purple_robot_manager.scripting.JavaScriptEngine;
@SuppressLint("DefaultLocale")
public class JavascriptFeature extends Feature
{
private String _name = null;
private String _title = null;
private String _script = null;
private String _formatter = null;
private boolean _embedded = false;
private final List<String> _sources = new ArrayList<>();
@Override
public String getPreferenceKey() {
return "features_javascript";
}
public JavascriptFeature()
{
throw new RuntimeException("Invalid constructor. Please use JavascriptFeature(scriptName) instead...");
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_misc_category);
}
public JavascriptFeature(String title, String name, String script, String formatter, List<String> sources, boolean embedded)
{
this._name = name;
this._title = title;
this._script = script;
this._formatter = formatter;
this._embedded = embedded;
this._sources.addAll(sources);
}
public boolean embedded()
{
return this._embedded;
}
@Override
protected String featureKey()
{
return this._name.replaceAll(".", "_");
}
@Override
public String name(Context context)
{
return "javascript_" + this._name;
}
@Override
public String title(Context context)
{
return this._title;
}
public static String[] availableFeatures(Context context)
{
return context.getResources().getStringArray(R.array.js_feature_files);
}
public static String[] availableFeatureNames(Context context)
{
return context.getResources().getStringArray(R.array.js_feature_names);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean("config_feature_" + this.featureKey() + "_enabled", true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean("config_feature_" + this.featureKey() + "_enabled", false);
e.commit();
}
@Override
public boolean isEnabled(Context context)
{
boolean enabled = super.isEnabled(context);
SharedPreferences prefs = Probe.getPreferences(context);
if (enabled && prefs.getBoolean("config_feature_" + this.featureKey() + "_enabled", true))
return true;
return false;
}
public static String scriptForFeature(Context context, String filename)
{
String script = "";
try
{
AssetManager am = context.getAssets();
InputStream jsStream = am.open("js/features/" + filename);
// http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
Scanner js = new Scanner(jsStream);
Scanner s = js.useDelimiter("\\A");
if (s.hasNext())
script = s.next();
s.close();
js.close();
jsStream.close();
}
catch (IOException e)
{
LogManager.getInstance(context).logException(e);
}
return script;
}
@SuppressLint("DefaultLocale")
public void processData(final Context context, final JSONObject json)
{
if (this.isEnabled(context) == false)
return;
boolean sourceMatches = false;
try
{
String source = json.getString("PROBE");
for (int i = 0; i < this._sources.size() && sourceMatches == false; i++)
{
String probeSource = this._sources.get(i);
if (source != null && source.toLowerCase().equals(probeSource.toLowerCase()))
sourceMatches = true;
}
if (sourceMatches == false)
return;
}
catch (JSONException e)
{
}
if (this._script == null)
this._script = JavascriptFeature.scriptForFeature(context, this._name);
final JavascriptFeature me = this;
Runnable r = new Runnable()
{
@Override
public void run()
{
Looper.prepare();
String script = me._script;
script = "var probe = " + json.toString() + "; " + script;
JavaScriptEngine engine = new JavaScriptEngine(context);
try
{
Object o = engine.runScript(script);
Bundle bundle = new Bundle();
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
if (o instanceof String)
bundle.putString(Feature.FEATURE_VALUE, o.toString());
else if (o instanceof Double)
{
Double d = (Double) o;
bundle.putDouble(Feature.FEATURE_VALUE, d);
}
else if (o instanceof NativeObject)
{
NativeObject nativeObj = (NativeObject) o;
Bundle b = JavascriptFeature.bundleForNativeObject(nativeObj);
bundle.putParcelable(Feature.FEATURE_VALUE, b);
}
else
{
Log.e("PRM", "JS PLUGIN GOT UNKNOWN VALUE " + o);
if (o != null)
Log.e("PRM", "JS PLUGIN GOT UNKNOWN CLASS " + o.getClass());
}
me.transmitData(context, bundle);
}
catch (Exception e)
{
LogManager.getInstance(context).logException(e);
}
}
};
Thread t = new Thread(r);
t.start();
}
public static Bundle bundleForNativeObject(NativeObject obj)
{
Bundle b = new Bundle();
for (Object key : obj.keySet())
{
String keyString = key.toString();
Object value = obj.get(key);
if (value instanceof NativeObject)
b.putParcelable(keyString, JavascriptFeature.bundleForNativeObject((NativeObject) value));
else if (value instanceof NativeArray)
{
NativeArray array = (NativeArray) value;
ArrayList<Bundle> items = new ArrayList<>();
for (Object o : array.getIds())
{
int index = (Integer) o;
Object item = array.get(index);
if (item instanceof NativeObject)
items.add(JavascriptFeature.bundleForNativeObject((NativeObject) item));
}
b.putParcelableArrayList(keyString, items);
}
else if (value instanceof Double)
b.putDouble(keyString, (Double) value);
else if (value instanceof Integer)
b.putInt(keyString, (Integer) value);
else if (value == null)
b.putString(keyString, "(null)");
else
b.putString(keyString, value.toString());
}
return b;
}
@Override
public String summary(Context context)
{
return null;
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
Object value = bundle.get(Feature.FEATURE_VALUE);
if (this._formatter == null)
return String.format(context.getString(R.string.summary_javascript_feature), this._name, value);
String script = this._formatter;
if (value instanceof Double || value instanceof Integer)
script = "var result = " + value + "; " + script;
else if (value instanceof Bundle)
{
try
{
script = "var result = " + OutputPlugin.jsonForBundle((Bundle) value).toString() + "; " + script;
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
}
else
script = "var result = '" + value.toString() + "'; " + script;
JavaScriptEngine engine = new JavaScriptEngine(context);
Object o = engine.runScript(script);
return o.toString();
}
@Override
public JSONObject fetchSettings(Context context)
{
return null;
}
}